package it.softphone.commons.json.server;

import static it.softphone.commons.util.LogUtil.logger;
import it.softphone.commons.exception.NullParameterException;
import it.softphone.commons.json.server.converter.JSONCollectionConverter;
import it.softphone.commons.json.server.converter.JSONConverterProxy;
import it.softphone.commons.json.server.converter.JSONDateConverter;
import it.softphone.commons.json.server.converter.JSONStringConverter;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;


@SuppressWarnings("rawtypes")
public class JSONUtils {

	static {
		
		registerConverter( java.lang.String.class, new JSONStringConverter() );
		registerConverter( java.util.Date.class, new JSONDateConverter() );
		registerConverterCollection( java.util.List.class, new JSONCollectionConverter<java.util.List<?>>() );
		registerConverterCollection( java.util.Set.class, new JSONCollectionConverter<java.util.Set<?>>() );
	}

	public static final JSONConverter<Object> NullConverter = new JSONConverter<Object>() {

		public Object convert(Object value) {
			return (value==null) ? JSONObject.NULL : value;
		}

		public Object convert(Class type, Object value) {
			
			return ( JSONObject.NULL==value ) ? null : value;
		}

	};
	
	protected static final <T extends java.util.Collection<?>> void registerConverterCollection( Class<?> type, JSONConverter<T> c ) {
		ConvertUtils.register( new JSONConverterProxy<T>(c), type);		
	}
	public static final <T> void registerConverter( Class<T> type, JSONConverter<T> c ) {
	
		ConvertUtils.register( new JSONConverterProxy<T>(c), type);
	}
	
	public static final void unregisterConverter( Class<?> type ) {
		
		ConvertUtils.deregister(type);
	}
	
	/**
	 * 
	 * @param type
	 * @return
	 */
	public static final JSONConverter<?> lookupConverter(Class<?> type) {
		Converter c = ConvertUtils.lookup(type);
		
		JSONConverter<?> result = NullConverter;
		if( c instanceof JSONConverter ) 
			result = (JSONConverter<?>) c;
		
		return result;
	}
	
	/**
	 * 
	 * @param e
	 * @return
	 */
	public final static String toJSON( final Throwable e ) {
		if( e == null ) throw new NullParameterException( "e" );
		
		final Throwable cause = ( e.getCause() == null ) ? e : e.getCause();
		
		try {
			it.softphone.commons.json.server.JSONObject result = buildException( new it.softphone.commons.json.server.JSONObject(), cause , 10 );
			
			return result.toString(4);
			
		} catch (JSONException e1) {
			logger.warn( String.format("error converting exception class [%s] to JSON", e.getClass().getName()), e );

			return "{ exception:{message:'see log for detail'} }";
		}
		
	}

	/**
	 * 
	 * @param e
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public final static it.softphone.commons.json.server.JSONObject toJSONObject( final Object e ) {
		assert e != null;
		if( e == null ) throw new NullParameterException( "e" );
		
		JSONConverter<Object> converter = (JSONConverter<Object>) lookupConverter( e.getClass() );
		
		Object result = converter.convert(e);
		
		final String msg = "object provided cannot be translate in json object";
		assert result instanceof it.softphone.commons.json.server.JSONObject : msg;
		if( ! (result instanceof it.softphone.commons.json.server.JSONObject ) ) {
			throw new IllegalArgumentException( msg);			
		}
		
		return (it.softphone.commons.json.server.JSONObject) result;
	}
	
	/**
	 * 
	 * @param e
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public final static it.softphone.commons.json.server.JSONArray toJSONArray( final Object e ) {
		assert e != null;
		if( e == null ) throw new NullParameterException( "e" );
		
		JSONConverter<Object> converter = (JSONConverter<Object>) lookupConverter( e.getClass() );
		
		Object result = converter.convert(e);
		
		final String msg = "object provided cannot be translate in json array";
		assert result instanceof it.softphone.commons.json.server.JSONArray : msg;
		if( ! (result instanceof it.softphone.commons.json.server.JSONArray ) ) {
			throw new IllegalArgumentException( msg);			
		}
		
		return (it.softphone.commons.json.server.JSONArray) result;
	}

	/**
	 * 
	 * @param e
	 * @return
	 */
	public final static String toJSON( Object e ) {
		if( e == null ) throw new NullParameterException( "e" );
		
		if( e instanceof Exception ) {
			return toJSON( (Exception)e );
		}
		
		Object result = it.softphone.commons.json.server.JSONObject.wrap(e);
		return (result==null) ?  "{}" : result.toString();
			
		
	}

	/**
	 * 
	 * @param target
	 * @param source
	 * @return
	 * @throws JSONException
	 */
	public static final <T> T copyTo( T target, it.softphone.commons.json.server.JSONObject source) throws JSONException {
		if( source == null ) throw new NullParameterException( "source" );
		if( target == null ) throw new NullParameterException( "target" );
		
		try {
			BeanUtils.copyProperties(target, source );
		} catch (Exception e) {
			throw new JSONException( e );
		} 
		
		return target;
	}

	/**
	 * 
	 * @param e
	 * @param depth
	 * @return
	 * @throws JSONException
	 */
	public static final  JSONObject buildException( Throwable e, int depth) throws JSONException {
	
		return buildException( new it.softphone.commons.json.server.JSONObject(), e, depth );	
		
	}

	/**
	 * 
	 * @param result
	 * @param e
	 * @param depth
	 * @return
	 * @throws JSONException
	 */
	public static final  JSONObject buildException(JSONObject result, Throwable e, int depth) throws JSONException {
		if( result==null ) throw new NullParameterException("result");
		if( e==null ) throw new NullParameterException("e");
		if( depth<=0 ) throw new IllegalArgumentException("depth is less or equalt to 0!");
		
		it.softphone.commons.json.server.JSONObject o = new it.softphone.commons.json.server.JSONObject();
		
		o.put( "message", e.getMessage());
		o.put( "localizedMessage", e.getLocalizedMessage());
		

		StackTraceElement stTrace[]= e.getStackTrace();
		if( stTrace != null ) {
			
			int safeDepth = Math.min(stTrace.length, depth);

			String st[] = new String[safeDepth];
			
			for( int i=0; i < safeDepth; ++i ) {
				st[i] = stTrace[i].toString();
			}
			
			o.put( "stackTrace", st);
		}
		
		result.put( "exception", o);
		
    	return result;
	}

}
